﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PClusterModel.Infrastructure;
using System.Collections;
using PClusterModel.Utils;
using PClusterModel.Comparer;

namespace PClusterModel.Cluster
{
    public class PClusterizer : ClusterizerBase
    {
        public delegate void ClusteringFinishedEventHandler(object sender, ClusteringFinishedEventArgs e);

        private byte trashold;
        private int minNumberOfObjects;
        private int minNumberOfConditions;
        private MicroarrayDataMatrix dataSet;
        private Dictionary<IMicroarrayDataObject, Block> blocks;
        private PatternType patternType;

        public event EventHandler ClusteringStarting;
        public event EventHandler PrunningFinished;
        public event EventHandler MdsCalculationFinished;
        public event ClusteringFinishedEventHandler ClusteringFinished;

        #region Properties

        public MicroarrayDataMatrix DataSet
        {
            get { return dataSet; }
        }

        public byte Trashold
        {
            get { return trashold; }
        }

        public int MinNumberOfObjects
        {
            get { return minNumberOfObjects; }
        }

        public int MinNumberOfConditions
        {
            get { return minNumberOfConditions; }
        }

        public PatternType PatternType
        {
            get { return patternType; }
        }

        public Dictionary<IMicroarrayDataObject, Block> Blocks
        {
            get { return blocks; }
        }
        #endregion

        public PClusterizer(MicroarrayDataMatrix dataSet, byte trashold, int minNumberOfRows, int minNumberOfColumns, PatternType patternType)
        {
            this.dataSet = dataSet;
            this.trashold = trashold;
            this.minNumberOfConditions = minNumberOfColumns;
            this.minNumberOfObjects = minNumberOfRows;
            this.patternType = patternType;
            this.blocks = new Dictionary<IMicroarrayDataObject, Block>();
        }

        public override IList<IMicroarrayCluster> Clusterize()
        {
            OnClusteringStarting();

            MaximalDimensionSetFinder mdsFinder = this.GetMaximalDimensionSetFinder(true);
            mdsFinder.CalculateMaximalDimensionSetsAndPlaceIntoBlocks();
            OnMdsCalculationFinished();

            BlockPruner pruner = new BlockPruner(this);
            pruner.PruneBlocks();
            OnPrunningFinished();

            IList<IMicroarrayCluster> allClusters = this.FindAllClustersInBlocks();

            OnClusteringFinished(allClusters);
            return allClusters;
        }


        private IList<IMicroarrayCluster> FindAllClustersInBlocks()
        {
            List<IMicroarrayCluster> clusters = new List<IMicroarrayCluster>();
            bool hasClusters = false;
            Cliquer cliquer = new Cliquer(this.blocks);

            foreach (Block block in this.blocks.Values)
            {
                IList<BitArray> allBitMapsInBlock = block.MaximalDimensionSets.SelectMany(b => b.ConditionBitMaps)
                    .Distinct(BitarrayEqualityComparer.Instance).ToList();

                do
                {
                    hasClusters = false;

                    foreach (BitArray bitmap in allBitMapsInBlock)
                    {
                        List<IMicroarrayDataObject> objectsClusteredWithBitmap = block.GetDataObjectsClusteredOnConditionSet(bitmap);
                        if (objectsClusteredWithBitmap.Count >= minNumberOfObjects)
                        {
                            bool isSubsetOfExistingCluster = this.IsSubsetOfExistingCluster(objectsClusteredWithBitmap, bitmap, clusters);
                            if (!isSubsetOfExistingCluster)
                            {
                                Clique clique = cliquer.FindMaximalClique(objectsClusteredWithBitmap, bitmap, this.minNumberOfObjects);
                                if (clique != null)
                                {
                                    isSubsetOfExistingCluster = this.IsSubsetOfExistingCluster(clique, bitmap, clusters);
                                    if (!isSubsetOfExistingCluster)
                                    {
                                        PCluster cluster = new PCluster(clique, bitmap);
                                        hasClusters = true;
                                        clusters.Add(cluster);
                                    }
                                    //TODO: Prune the related blocks - remove the bitmap 
                                    //entries from the blocks in the cluster which are != current block.
                                    //That way we avoid having subset clusters
                                    //MaximalDimensionSet mds = block.GetMaximalDimensionSetForBitmap(bitmap);
                                    //if (mds != null)
                                    //{
                                    //    //this.PruneRelatedBlocks(mds,block);
                                    //}
                                }
                            }
                        }
                    }
                    if (hasClusters)
                    {
                        allBitMapsInBlock = this.CombineBitMapPairs(allBitMapsInBlock);
                    }
                }
                while (hasClusters);
            }

            return clusters;
        }

        private bool IsSubsetOfExistingCluster(List<IMicroarrayDataObject> objectsClusteredWithBitmap, BitArray bitmap, List<IMicroarrayCluster> clusters)
        {
            if (clusters.Count == 0)
            {
                return false;
            }
            bool isSubset = false;

            foreach (PCluster cluster in clusters)
            {
                isSubset = Utilities.IsBitmapSubsetOfSecondBitmap(bitmap, cluster.ClusteringConditions);
                if (isSubset)
                {
                    foreach (IMicroarrayDataObject dataObject in objectsClusteredWithBitmap)
                    {
                        if (!cluster.ObjectsInCluster.Contains(dataObject))
                        {
                            isSubset = false;
                            break;
                        }
                    }
                    if (isSubset)
                    {
                        break;
                    }
                }
            }
            return isSubset;
        }

        private IList<BitArray> CombineBitMapPairs(IList<BitArray> allBitmaps)
        {
            List<BitArray> result = new List<BitArray>();
            Comparer.BitarrayEqualityComparer comparer = Comparer.BitarrayEqualityComparer.Instance;
            int count = allBitmaps.Count;
            int[] hashes = new int[count];
            HashSet<int> hashSet = new HashSet<int>();

            for (int i = 0; i < count; i++)
            {
                int hash = comparer.GetHashCode(allBitmaps[i]);
                hashes[i] = hash;
                hashSet.Add(hash);
            }

            BitArray firstBitmap, secondBitmap, combinedBitmap;

            for (int i = 0; i < count - 1; i++)
            {
                for (int j = i + 1; j < count; j++)
                {
                    int newHash = hashes[i] & hashes[j];

                    if (Utilities.GetNumberOfSetBits(newHash) >= minNumberOfConditions)
                    {
                        if (hashSet.Contains(newHash))
                            continue;

                        hashSet.Add(newHash);
                        firstBitmap = allBitmaps[i];
                        secondBitmap = allBitmaps[j];
                        combinedBitmap = new BitArray(firstBitmap);
                        combinedBitmap.And(secondBitmap);
                        result.Add(combinedBitmap);
                    }
                }
            }
            return result;
        }

        private MaximalDimensionSetFinder GetMaximalDimensionSetFinder(bool parallel)
        {
            if (parallel)
            {
                if (this.patternType == PClusterModel.PatternType.Shifting)
                {
                    return new MaximalDimensionSetFinderParallel(this); ;
                }
                else
                {
                    return new MaximalDimensionSetFinderParallelScaling(this);
                }
            }
            else
            {
                if (this.patternType == PClusterModel.PatternType.Shifting)
                {
                    return new MaximalDimensionSetFinder(this);
                }
                else
                {
                    return new MaximalDimensionSetFinderScaling(this);
                }
            }
        }


        private void OnClusteringStarting()
        {
            if (this.ClusteringStarting != null)
                this.ClusteringStarting(this, new EventArgs());
        }

        private void OnClusteringFinished(IList<IMicroarrayCluster> clusters)
        {
            if (this.ClusteringFinished != null)
                this.ClusteringFinished(this, new ClusteringFinishedEventArgs(clusters));
        }

        private void OnPrunningFinished()
        {
            if (this.PrunningFinished != null)
                this.PrunningFinished(this, new EventArgs());
        }

        private void OnMdsCalculationFinished()
        {
            if (this.MdsCalculationFinished != null)
                this.MdsCalculationFinished(this, new EventArgs());
        }

    }
}